# One can use this script to automate comparisons running an EDB with many
# different combinations of IDB files or just to run with a certain group of
# files.

# Example 1: python postprocess.py -c totesComparison example1facts.dlv
# ... will confirm that any method of producing the total order against this EDB file will produce the same result. If one wants to then examine that result, it is simple to run:

# Example 2: python postprocess.py -d computePOTotes -f succ
# ... this produces the expected seven worlds.


import gflags
import random
import subprocess as subp
import sys


fileBundles = {
	'computePOTotes':('CommonAuxiliary.dlv', 'ComputePO.dlv', 'CreateTO.dlv'),
	'guessTimeTotes':('CommonAuxiliary.dlv', 'GuessTime.dlv', 'GuessTimeFilter.dlv', 'GuessTimeAdapter.dlv'),
	'guessPOTotes':('CommonAuxiliary.dlv', 'GuessPO.dlv', 'GuessPOFilter.dlv', 'GuessPOAdapter.dlv'),
	'guessPOLeq':('CommonAuxiliary.dlv', 'GuessPO.dlv', 'GuessPOFilter.dlv', 'AbducePartialOrder.dlv'),
	'computePO':('CommonAuxiliary.dlv', 'ComputePO.dlv')
}


def _trunc(s):
	s = str(s)
	if len(s) > 50:
		c = len(s) - 50
		return '%s... (%d elided)' % (s[:50], c)
	else:
		return s


def full_summary(canonicalOutput):

	s = set(canonicalOutput.values())
	if len(s) == 1:
		print 'All are identical'
	else:
		matches = set()
		for i in canonicalOutput.keys():
			for j in canonicalOutput.keys():
				if i == j:
					continue
				if canonicalOutput[i] == canonicalOutput[j]:
					matches.add(tuple(sorted((i, j))))
		for match in matches:
			print '%s and %s coincide' % match
		print 'There are %d distinct returns' % (len(s),)


def containment_summary(output):
	print 'Have output for keys:', output.keys()
	for key in output.keys():
		data = output[key]
		print '%s -> %s' % (key, _trunc(data))
		print '%s -> #%s' % (key, len(data))

	searchFor = None
	searchIn = None

	searchForKey = None
	searchInKey = None

	assert len(output) == 2

	for key in output.keys():
		data = output[key]
		if len(data) == 1:
			searchFor = data[0]
			searchForKey = key
		else:
			searchIn = data
			searchInKey = key

	if searchFor is None or searchIn is None:
		print 'Could not determine containment query.'
	else:

		print 'This is what we are looking for:'
		print searchFor
		print 'This is an example item out of what we are looking through:'
		world = searchIn[0]

		focusOnWorld = 6

		for a, b in enumerate(searchIn):
			focusOnThis = (a == focusOnWorld)
			if not focusOnThis:
				continue
			print 'World %d:' % (a,)
			for c, d in enumerate(searchIn):
				if c == a:
					continue
				print 'Comparing %d with world %d:' % (a, c)
				aFacts = set(b.split())
				cFacts = set(d.split())
				print 'Shared facts:', len(aFacts.intersection(cFacts))
				d = aFacts.difference(cFacts)
				print 'Only in home team:', len(d)
				for f in d:
					print '*', f
				d = cFacts.difference(aFacts)
				print 'Only in visiting team:', len(d)
				for f in d:
					print '*', f
			print '========='
			for i in b.split():
				print '*', i
			print '----------------------'

		print 'Searching for %s in %s...' % (searchForKey, searchInKey)
		print 'Yes!' if searchFor in searchIn else 'No.'


comparisons = {
	# totesComparison compares total orders computed by deductive computation a
	# la the paper, abducing a total order, and abducing a partial order.
	'totesComparison':{'bundles':('computePOTotes', 'guessTimeTotes', 'guessPOTotes'), 'project':'succ', 'summary':full_summary},

	# leqComparison compares the partial order computed deductively with those abduced.
	'leqComparison':{'bundles':('guessPOLeq', 'computePO'), 'project':'leq', 'summary':containment_summary},

	# tleqComparison is like leqComparison, but explicitly computes the
	# transitive closure of the partial order at the end.
	'tleqComparison':{'bundles':('guessPOLeq', 'computePO'), 'project':'tleq', 'summary':containment_summary}
}


FLAGS = gflags.FLAGS
gflags.DEFINE_string('direct', None, 'File bundle to run with. Options: ' + ', '.join(fileBundles.keys()), short_name='d')
gflags.DEFINE_string('comparison', None, 'Comparison. Options: ' + ', '.join(comparisons.keys()) + '. These map to a list of lists of DLV source files. The same EDB data is run against each set of DLV source files and the final results are compared. The comparison options also map to information about how they are to be compared.', short_name='c')
gflags.DEFINE_string('filter', None, 'Predicate filter expression', short_name='f')
gflags.DEFINE_string('timeComparison', None, 'Timing of various methods. Options: ' + ', '.join(comparisons.keys()) + '. These map to a list of lists of DLV source files. The same EDB data is run against each set of DLV source files and the computations are timed.', short_name='t')

gflags.RegisterValidator('direct', lambda value: value in fileBundles.keys() or value is None, message='Not a valid file bundle')
gflags.RegisterValidator('comparison', lambda value: value in comparisons.keys() or value is None, message='Not a valid comparison')
gflags.RegisterValidator('timeComparison', lambda value: value in comparisons.keys() or value is None, message='Not a valid time comparison')


def main(argv):
	print 'Hello, I am your post-processor for today.'
	try:
		argv = FLAGS(argv)
		print argv
	except gflags.FlagsError, e:
		print '%s\\nUsage: %s ARGS \\n%s' % (e, sys.argv[0], FLAGS)
		sys.exit(1)

	filename = None
	try:
		filename = argv[1]
	except IndexError:
		print_usage()
		sys.exit(1)

	if FLAGS.direct is not None:
		filenames = ' '.join(fileBundles[FLAGS.direct])
		command = None
		if FLAGS.filter is None:
			command = 'dlv -silent %s %s' % (filenames, filename) # (generics,EDB)
		else:
			command = 'dlv -silent -filter=%s %s %s' % (FLAGS.filter, filenames, filename) # (generics,EDB)
		p = subp.Popen(command, stdin=subp.PIPE, stdout=subp.PIPE, stderr=subp.STDOUT, shell=True)
		p.stdin.close()
		for line in p.stdout.readlines():
			print line
		sys.exit(0)

	elif FLAGS.comparison is not None:
		compareOutput(filename, FLAGS.comparison)
	
	elif FLAGS.timeComparison is not None:
		for interval in (200,):
			timeRunning(filename, FLAGS.timeComparison, time=interval)


def print_usage():
	print 'Run with --help for command line options.'


def timeRunning(filename, comparison_type, time=20):
	my_comp = comparisons[comparison_type]

	my_file_bundles = my_comp['bundles']

	canonicalOutput = {}
	for bundleKey in my_file_bundles:
		fileBundle = fileBundles[bundleKey]
		filenames = ' '.join(fileBundle)

		time_filename = '/tmp/' + str(random.randint(1, 10000000))
		f = open(time_filename, 'w')
		f.write('time(1..%d).\n' % (time,))
		f.close()

		command = 'time dlv -silent %s %s %s' % (filenames, filename, time_filename)
		p = subp.Popen(command, stdin=subp.PIPE, stdout=subp.PIPE, stderr=subp.STDOUT, shell=True)
		p.stdin.close()
		lines = p.stdout.readlines()
		lines = [x.strip() for x in lines]
		lines = lines[-3:]
		userline = lines[1]
		userline = userline[len('user'):]
		canonicalOutput[bundleKey] = userline
	for (method, duration) in canonicalOutput.items():
		print '%d\t%s\t%s' % (time, method, duration)


def compareOutput(filename, comparison_type):
	my_comp = comparisons[comparison_type]

	project_predicate = my_comp['project']
	my_file_bundles = my_comp['bundles']
	summary_function = my_comp['summary']

	canonicalOutput = {}
	for bundleKey in my_file_bundles:
		fileBundle = fileBundles[bundleKey]
		filenames = ' '.join(fileBundle)
		command = 'dlv -silent -filter=%s %s %s' % (project_predicate, filenames, filename) # (generics,EDB)
		p = subp.Popen(command, stdin=subp.PIPE, stdout=subp.PIPE, stderr=subp.STDOUT, shell=True)
		p.stdin.close()
		lines = p.stdout.readlines()
		lines = [x.strip() for x in lines]
		# Remove braces
		lines = [x[1:-1] for x in lines]

		# Split up facts
		lines = [x.split(', ') for x in lines]

		lines = [sorted(x) for x in lines]

		lines = [' '.join(x) for x in lines]

		canonicalOutput[bundleKey] = tuple(sorted(lines))

	summary_function(canonicalOutput)



def compare(a, b, aName, bName):
	sa = set(a)
	sb = set(b)
	intersect = sa.intersection(sb)
	onlyA = sa.difference(sb)
	onlyB = sb.difference(sa)

	print 'Shared successor facts:', len(intersect)
	print 'Only %s:' % (aName,), len(onlyA)
	print 'Only %s:' % (bName,), len(onlyB)

	print 'Shared:'
	for x in intersect:
		print x

	print '-----------'

	print aName, ':'
	for x in onlyA:
		print x

	print '-----------'

	print bName, ':'
	for x in onlyB:
		print x


if __name__ == '__main__':
	main(sys.argv)
